//======================================================================================
/** \file  avr_adc.cc
 *    This file contains a very, very simple A/D converter driver. WARNING: It has bugs!
 *    It's only here as an example. It should not be used in a meaningful project. 
 *
 *  Revisions:
 *    \li  01-15-08 JRR Original (somewhat useful) file
 *
 *  License:
 *    This file released under the Lesser GNU Public License. The program is intended
 *    for educational use only, but its use is not restricted thereto. 
 */
//======================================================================================

#include <stdlib.h>                         // Include standard library header files
#include <avr/io.h>
#include "rs232int.h"                       // Include header for serial port class
#include "avr_adc.h"                        // Include header for the A/D class


#define ADC_PRESCALE		(ADPS1)			///< Default prescaler setting (F_cpu / 4)
#define ADC_RETRIES			10000			///< Retries before giving up on conversion


//-------------------------------------------------------------------------------------
/** This union holds the two bytes which are read from the A/D converter's result
 *  registers after a conversion has been completed. They're referenced also as one
 *  16-bit integer which can be returned as the result of a conversion.
 */

union ADC_result
{
	uint8_t bytes[2];						///< The bytes in the number
	uint16_t word;							///< The whole 16-bit number
};


//-------------------------------------------------------------------------------------
/** This constructor sets up an A/D converter. How does it do so?
 *  @param p_serial_port A pointer to the serial port which writes debugging info. 
 */

avr_adc::avr_adc (base_text_serial* p_serial_port)
    {
    ptr_to_serial = p_serial_port;          // Store the serial port pointer locally

    ADMUX = 0;                              // Set reference to AVCC pin voltage
    ADCSRA = (1 << ADEN) | ADC_PRESCALE;    // Set clock prescaler and turn A/D on
    }


//-------------------------------------------------------------------------------------
/** This method takes one A/D reading from the given channel, and...does something...
 *  \param  channel The A/D channel which is being read must be from 0 to 7
 *  \return The result of the A/D conversion, or 0xFFFF if there was a timeout
 */

uint16_t avr_adc::read_once (uint8_t channel)
{
	uint16_t counter;						// Counter waits for timeouts
	ADC_result what_read;					// Puts together result read from A/D

	// Set the channel
	ADMUX &= 0b11111000;                    // Clear the channel bits
	ADMUX |= (channel & 0b00000111);        // Put channel bits in the register

	// Start a conversion
	ADCSRA |= (1 << ADSC);

	// Wait until a timeout, or the conversion is finished
	for (counter = 0; ADCSRA & (1 << ADSC); counter++)
		{
		if (counter > ADC_RETRIES)
			return (0xFFFF);
		}

	what_read.bytes[0] = ADCL;
	what_read.bytes[1] = ADCH & 0b00000011;

	// There was no timeout, but we didn't get any reading either, so return 0
	return (what_read.word);
}


//-------------------------------------------------------------------------------------
/** This method takes two A/D readings from the given channel, throwing out the first
 *  one (which is often junky) and returning the second. 
 *  \param  channel The A/D channel which is being read must be from 0 to 7
 *  \return The result of the A/D conversion, or 0xFFFF if there was a timeout
 */

unsigned int avr_adc::read_twice (unsigned char channel)
{
	unsigned int counter;                   // Counter waits for timeouts
	ADC_result what_read;                   // Puts together result read from A/D

	// Set the channel
	ADMUX &= 0b11111000;                    // Clear the channel bits
	ADMUX |= (channel & 0b00000111);        // Put channel bits in the register

	// Start a conversion
	ADCSRA |= (1 << ADSC);

	// Wait until a timeout, or the conversion is finished
	for (counter = 0; ADCSRA & (1 << ADSC); counter++)
		{
		if (counter > ADC_RETRIES)
			return (0xFFFF);
		}

	// Read and discard the A/D contents
	unsigned char temp_char = ADCH;

	// Start a second conversion
	ADCSRA |= (1 << ADSC);

	// Wait until a timeout, or the conversion is finished
	for (counter = 0; ADCSRA & (1 << ADSC); counter++)
	{
		if (counter > ADC_RETRIES)
		{
			return (0xFFFF);
		}
	}

	what_read.bytes[0] = ADCL;
	what_read.bytes[1] = ADCH & 0b00000011;

	// There was no timeout, so return the reading
	return (what_read.word);
}


//-------------------------------------------------------------------------------------
/** This function sets the A/D multiplexer to read from the given channel, then 
 *  reads that channel the given number of times (up to a maximum of 32) and computes
 *  the average of the readings.  This can help reduce noise.  Note that there are
 *  many ways to digitally filter a signal; this is just one very crude, simple way. 
 *  \param  channel  The A/D channel which is being read must be from 0 to 7
 *  \param  samples  The number of samples to be taken and averaged
 *  \return The averaged result of the A/D conversions, or -1 if an error occurred
 */

uint16_t avr_adc::read_oversampled (uint8_t channel, uint8_t samples)
{
	uint8_t wait_count;					// Counter for timing out conversion wait
	uint8_t samp_count;					// Storage for status read from A/D unit
	uint16_t sum = 0;					// The sum of the A/D readings taken so far
	ADC_result what_read;				// Puts together result read from A/D

	// Set the channel
	ADMUX &= 0b11111000;                    // Clear the channel bits
	ADMUX |= (channel & 0b00000111);        // Put channel bits in the register

	// Run the oversampling loop, reading from the converter many times.  A maximum
	// of 32 samples are allowed; any more could overflow the 16-bit averaging sum
	if (samples > 32)
	{
		return (-1);
	}

	for (samp_count = 0; samp_count < samples; samp_count++)
	{
		ADCSRA |= (1 << ADSC);			// Start a conversion

		// Wait until a timeout, or the conversion is finished
		for (wait_count = 0; ADCSRA & (1 << ADSC); wait_count++)
		{
			if (wait_count > ADC_RETRIES)
			{
				return (0xFFFF);
			}
		}

		// The low byte must be read before the high byte for correct data
		what_read.bytes[0] = ADCL;
		what_read.bytes[1] = ADCH & 0b00000011;
		sum += what_read.word;
	}

	// Return the result, computed as unsigned, in a signed int (after division)
	return ((short int)(sum / samples));
}
